<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_33</span></h2>

  <p>ackage，那么我们对这一行剩下的东西就没什么兴趣了。其他所有东西肯定是一个标识符（这是我们感兴趣的），或者是一个关键字（对此不感兴趣，但它们采用的肯定是小写形式，所以不必兴师动众地检查它们）。它们将加入到identMap。</p>

  <p>discardLine()方法是一个简单的工具，用于查找行末位置。注意每次得到一个新记号时，都必须检查行末。</p>

  <p>只要在主解析循环中碰到一个正斜杠，就会调用eatComments()方法。然而，这并不表示肯定遇到了一条注释，所以必须将接着的记号提取出来，检查它是一个正斜杠（那么这一行会被丢弃），还是一个星号。但假如两者都不是，意味着必须在主解析循环中将刚才取出的记号送回去！幸运的是，pushBack()方法允许我们将当前记号“压回”输入数据流。所以在主解析循环调用nextToken()的时候，它能正确地得到刚才送回的东西。</p>

  <p>为方便起见，classNames()方法产生了一个数组，其中包含了classes集合中的所有名字。这个方法未在程序中使用，但对代码的调试非常有用。</p>

  <p>接下来的两个方法是实际进行检查的地方。在checkClassNames()中，类名从classMap提取出来（请记住，classMap只包含了这个目录内的名字，它们按文件名组织，所以文件名可能伴随错误的类名打印出来）。为做到这一点，需要取出每个关联的Vector，并遍历其中，检查第一个字符是否为小写。若确实为小写，则打印出相应的出错提示消息。</p>

  <p>在checkIdentNames()中，我们采用了一种类似的方法：每个标识符名字都从identMap中提取出来。如果名字不在classes列表中，就认为它是一个标识符或者关键字。此时会检查一种特殊情况：如果标识符的长度等于3或者更长，而且所有字符都是大写的，则忽略此标识符，因为它可能是一个static final值，比如TT_EOF。当然，这并不是一种完美的算法，但它假定我们最终会注意到任何全大写标识符都是不合适的。</p>

  <p>这个方法并不是报告每一个以大写字符开头的标识符，而是跟踪那些已在一个名为reportSet()的Vector中报告过的。它将Vector当作一个“集合”对待，告诉我们一个项目是否已在那个集合中。该项目是通过将文件名和标识符连接起来生成的。若元素不在集合中，就加入它，然后产生报告。</p>

  <p>程序列表剩下的部分由main()构成，它负责控制命令行参数，并判断我们是准备在标准Java库的基础上构建由一系列类名构成的“仓库”，还是想检查已写好的那些代码的正确性。不管在哪种情况下，都会创建一个ClassScanner对象。</p>

  <p>无论准备构建一个“仓库”，还是准备使用一个现成的，都必须尝试打开现有仓库。通过创建一个File对象并测试是否存在，就可决定是否打开文件并在ClassScanner中装载classes这个Properties列表（使用load()）。来自仓库的类将追加到由ClassScanner构建器发现的类后面，而不是将其覆盖。如果仅提供一个命令行参数，就意味着自己想对类名和标识符名字进行一次检查。但假如提供两个参数（第二个是"-a"），就表明自己想构成一个类名仓库。在这种情况下，需要打开一个输出文件，并用Properties.save()方法将列表写入一个文件，同时用一个字串提供文件头信息。</p>

  <p>17.2 方法查找工具</p>

  <p>第11章介绍了Java 1.1新的“反射”概念，并利用这个概念查询一个特定类的方法――要么是由所有方法构成的一个完整列表，要么是这个列表的一个子集（名字与我们指定的关键字相符）。那个例子最大的好处就是能自动显示出所有方法，不强迫我们在继承结构中遍历，检查每一级的基础类。所以，它实际是我们节省编程时间的一个有效工具：因为大多数Java方法的名字都规定得非常全面和详尽，所以能有效地找出那些包含了一个特殊关键字的方法名。若找到符合标准的一个名字，便可根据它直接查阅联机帮助文档。</p>

  <p>但第11的那个例子也有缺陷，它没有使用AWT，仅是一个纯命令行的应用。在这儿，我们准备制作一个改进的GUI版本，能在我们键入字符的时候自动刷新输出，也允许我们在输出结果中进行剪切和粘贴操作：</p>

  <p>//: DisplayMethods.java</p>

  <p>// Display the methods of any class inside</p>

  <p>// a window. Dynamically narrows your search.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>import java.lang.reflect.*;</p>

  <p>import java.io.*;</p>

  <p>public class DisplayMethods extends Applet {</p>

  <p>Class cl;</p>

  <p>Method[] m;</p>

  <p>Constructor[] ctor;</p>

  <p>String[] n = new String[0];</p>

  <p>TextField</p>

  <p>name = new TextField(40),</p>

  <p>searchFor = new TextField(30);</p>

  <p>Checkbox strip =</p>

  <p>new Checkbox("Strip Qualifiers");</p>

  <p>TextArea results = new TextArea(40, 65);</p>

  <p>public void init() {</p>

  <p>strip.setState(true);</p>

  <p>name.addTextListener(new NameL());</p>

  <p>searchFor.addTextListener(new SearchForL());</p>

  <p>strip.addItemListener(new StripL());</p>

  <p>Panel</p>

  <p>top = new Panel(),</p>

  <p>lower = new Panel(),</p>

  <p>p = new Panel();</p>

  <p>top.add(new Label("Qualified class name:"));</p>

  <p>top.add(name);</p>

  <p>lower.add(</p>

  <p>new Label("String to search for:"));</p>

  <p>lower.add(searchFor);</p>

  <p>lower.add(strip);</p>

  <p>p.setLayout(new BorderLayout());</p>

  <p>p.add(top, BorderLayout.NORTH);</p>

  <p>p.add(lower, BorderLayout.SOUTH);</p>

  <p>setLayout(new BorderLayout());</p>

  <p>add(p, BorderLayout.NORTH);</p>

  <p>add(results, BorderLayout.CENTER);</p>

  <p>}</p>

  <p>class NameL implements TextListener {</p>

  <p>public void textValueChanged(TextEvent e) {</p>

  <p>String nm = name.getText().trim();</p>

  <p>if(nm.length() == 0) {</p>

  <p>results.setText("No match");</p>

  <p>n = new String[0];</p>

  <p>return;</p>

  <p>}</p>

  <p>try {</p>

  <p>cl = Class.forName(nm);</p>

  <p>} catch (ClassNotFoundException ex) {</p>

  <p>results.setText("No match");</p>

  <p>return;</p>

  <p>}</p>

  <p>m = cl.getMethods();</p>

  <p>ctor = cl.getConstructors();</p>

  <p>// Convert to an array of Strings:</p>

  <p>n = new String[m.length + ctor.length];</p>

  <p>for(int i = 0; i &lt; m.length; i++)</p>

  <p>n[i] = m[i].toString();</p>

  <p>for(int i = 0; i &lt; ctor.length; i++)</p>

  <p>n[i + m.length] = ctor[i].toString();</p>

  <p>reDisplay();</p>

  <p>}</p>

  <p>}</p>

  <p>void reDisplay() {</p>

  <p>// Create the result set:</p>

  <p>String[] rs = new String[n.length];</p>

  <p>String find = searchFor.getText();</p>

  <p>int j = 0;</p>

  <p>// Select from the list if find exists:</p>

  <p>for (int i = 0; i &lt; n.length; i++) {</p>

  <p>if(find == null)</p>

  <p>rs[j++] = n[i];</p>

  <p>else if(n[i].indexOf(find) != -1)</p>

  <p>rs[j++] = n[i];</p>

  <p>}</p>

  <p>results.setText("");</p>

  <p>if(strip.getState() == true)</p>

  <p>for (int i = 0; i &lt; j; i++)</p>

  <p>results.append(</p>

  <p>StripQualifiers.strip(rs[i]) + "\n");</p>

  <p>else // Leave qualifiers on</p>

  <p>for (int i = 0; i &lt; j; i++)</p>

  <p>results.append(rs[i] + "\n");</p>

  <p>}</p>

  <p>class StripL implements ItemListener {</p>

  <p>public void itemStateChanged(ItemEvent e) {</p>

  <p>reDisplay();</p>

  <p>}</p>

  <p>}</p>

  <p>class SearchForL implements TextListener {</p>

  <p>public void textValueChanged(TextEvent e) {</p>

  <p>reDisplay();</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>DisplayMethods applet = new DisplayMethods();</p>

  <p>Frame aFrame = new Frame("Display Methods");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(500,750);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>}</p>

  <p>class StripQualifiers {</p>

  <p>private StreamTokenizer st;</p>

  <p>public StripQualifiers(String qualified) {</p>

  <p>st = new StreamTokenizer(</p>

  <p>new StringReader(qualified));</p>

  <p>st.ordinaryChar(' ');</p>

  <p>}</p>

  <p>public String getNext() {</p>

  <p>String s = null;</p>

  <p>try {</p>

  <p>if(st.nextToken() !=</p>

  <p>StreamTokenizer.TT_EOF) {</p>

  <p>switch(st.ttype) {</p>

  <p>case StreamTokenizer.TT_EOL:</p>

  <p>s = null;</p>

  <p>break;</p>

  <p>case StreamTokenizer.TT_NUMBER:</p>

  <p>s = Double.toString(st.nval);</p>

  <p>break;</p>

  <p>case StreamTokenizer.TT_WORD:</p>

  <p>s = new String(st.sval);</p>

  <p>break;</p>

  <p>default: // single character in ttype</p>

  <p>s = String.valueOf((char)st.ttype);</p>

  <p>}</p>

  <p>}</p>

  <p>} catch(IOException e) {</p>

  <p>System.out.println(e);</p>

  <p>}</p>

  <p>return s;</p>

  <p>}</p>

  <p>public static String strip(String qualified) {</p>

  <p>StripQualifiers sq =</p>

  <p>new StripQualifiers(qualified);</p>

  <p>String s = "", si;</p>

  <p>while((si = sq.getNext()) != null) {</p>

  <p>int lastDot = si.lastIndexOf('.');</p>

  <p>if(lastDot != -1)</p>

  <p>si = si.substring(lastDot + 1);</p>

  <p>s += si;</p>

  <p>}</p>

  <p>return s;</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>程序中的有些东西已在以前见识过了。和本书的许多GUI程序一样，这既可作为一个独立的应用程序使用，亦可作为一个程序片（Applet）使用。此外，StripQualifiers类与它在第11章的表现是完全一样的。</p>

  <p>GUI包含了一个名为name的“文本字段”（TextField），或在其中输入想查找的类名；还包含了另一个文本字段，名为searchFor，可选择性地在其中输入一定的文字，希望在方法列表中查找那些文字。Checkbox（复选框）允许我们指出最终希望在输出中使用完整的名字，还是将前面的各种限定信息删去。最后，结果显示于一个“文本区域”（TextArea）中。</p>

  <p>大家会注意到这个程序未使用任何按钮或其他组件，不能用它们开始一次搜索。这是由于无论文本字段还是复选框都会受到它们的“侦听者（Listener）对象的监视。只要作出一项改变，结果列表便会立即更新。若改变了name字段中的文字，新的文字就会在NameL类中捕获。若文字不为空，则在Class.forName()中用于尝试查找类。当然，在文字键入期间，名字可能会变得不完整，而Class.forName()会失败，这意味着它会“掷”出一个违例。该违例会被捕获，TextArea会随之设为“Nomatch”（没有相符）。但只要键入了一个正确的名字（大小写也算在内），Class.forName()就会成功，而getMethods()和getConstructors()会分别返回由Method和Constructor对象构成的一个数组。这些数组中的每个对象都会通过toString()转变成一个字串（这样便产生了完整的方法或构建器签名），而且两个列表都会合并到n中――一个独立的字串数组。数组n属于DisplayMethods类的一名成员，并在调用reDisplay()时用于显示的更新。</p>

  <p>若改变了Checkbox或searchFor组件，它们的“侦听者”会简单地调用reDisplay()。reDisplay()会创建一个临时数组，其中包含了名为rs的字串（rs代表“结果集”――Result Set）。结果集要么直接从n复制（没有find关键字），要么选择性地从包含了find关键字的n中的字串复制。最后会检查strip Checkbox，看看用户是不是希望将名字中多余的部分删除（默认为“是”）。若答案是肯定的，则用StripQualifiers.strip()做这件事情；反之，就将列表简单地显示出来。</p>

  <p>在init()中，大家也许认为在设置布局时需要进行大量繁重的工作。事实上，组件的布置完全可能只需要极少的工作。但象这样使用BorderLayout的好处是它允许用户改变窗口的大小，并特别能使TextArea（文本区域）更大一些，这意味着我们可以改变大小，以便毋需滚动即可看到更长的名字。</p>

  <p>编程时，大家会发现特别有必要让这个工具处于运行状态，因为在试图判断要调用什么方法的时候，它提供了最好的方法之一。</p>

  <p>17.3 复杂性理论</p>

  <p>下面要介绍的程序的前身是由Larry O'Brien原创的一些代码，并以由Craig Reynolds于1986年编制的“Boids”程序为基础，当时是为了演示复杂性理论的一个特殊问题，名为“凸显”（Emergence）。</p>

  <p>这儿要达到的目标是通过为每种动物都规定少许简单的规则，从而逼真地再现动物的群聚行为。每个动物都能看到看到整个环境以及环境中的其他动物，但它只与一系列附近的“群聚伙伴”打交道。动物的移动基于三个简单的引导行为：</p>

  <p>(1) 分隔：避免本地群聚伙伴过于拥挤。</p>

  <p>(2) 方向：遵从本地群聚伙伴的普遍方向。</p>

  <p>(3) 聚合：朝本地群聚伙伴组的中心移动。</p>

  <p>更复杂的模型甚至可以包括障碍物的因素，动物能预知和避免与障碍冲突的能力，所以它们能围绕环境中的固定物体自由活动。除此以外，动物也可能有自己的特殊目标，这也许会造成群体按特定的路径前进。为简化讨论，避免障碍以及目标搜寻的因素并未包括到这里建立的模型中。</p>

  <p>尽管计算机本身比较简陋，而且采用的规则也相当简单，但结果看起来是真实的。也就是说，相当逼真的行为从这个简单的模型中“凸显”出来了。</p>

  <p>程序以合成到一起的应用程序／程序片的形式提供：</p>

  <p>//: FieldOBeasts.java</p>

  <p>// Demonstration of complexity theory; simulates</p>

  <p>// herding behavior in animals. Adapted from</p>

  <p>// a program by Larry O'Brien lobrien@msn.com</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>import java.util.*;</p>

  <p>class Beast {</p>

  <p>int</p>

  <p>x, y, // Screen position</p>

  <p>currentSpeed; // Pixels per second</p>

  <p>float currentDirection; // Radians</p>

  <p>Color color; // Fill color</p>

  <p>FieldOBeasts field; // Where the Beast roams</p>

  <p>static final int GSIZE = 10; // Graphic size</p>

  <p>public Beast(FieldOBeasts f, int x, int y,</p>

  <p>float cD, int cS, Color c) {</p>

  <p>field = f;</p>

  <p>this.x = x;</p>

  <p>this.y = y;</p>

  <p>currentDirection = cD;</p>

  <p>currentSpeed = cS;</p>

  <p>color = c;</p>

  <p>}</p>

  <p>public void step() {</p>

  <p>// You move based on those within your sight:</p>

  <p>Vector seen = field.beastListInSector(this);</p>

  <p>// If you're not out in front</p>

  <p>if(seen.size() &gt; 0) {</p>

  <p>// Gather data on those you see</p>

  <p>int totalSpeed = 0;</p>

  <p>float totalBearing = 0.0f;</p>

  <p>float distanceToNearest = 100000.0f;</p>

  <p>Beast nearestBeast =</p>

  <p>(Beast)seen.elementAt(0);</p>

  <p>Enumeration e = seen.elements();</p>

  <p>while(e.hasMoreElements()) {</p>

  <p>Beast aBeast = (Beast) e.nextElement();</p>

  <p>totalSpeed += aBeast.currentSpeed;</p>

  <p>float bearing =</p>

  <p>aBeast.bearingFromPointAlongAxis(</p>

  <p>x, y, currentDirection);</p>

  <p>totalBearing += bearing;</p>

  <p>float distanceToBeast =</p>

  <p>aBeast.distanceFromPoint(x, y);</p>

  <p>if(distanceToBeast &lt; distanceToNearest) {</p>

  <p>nearestBeast = aBeast;</p>

  <p>distanceToNearest = distanceToBeast;</p>

  <p>}</p>

  <p>}</p>

  <p>// Rule 1: Match average speed of those</p>

  <p>// in the list:</p>

  <p>currentSpeed = totalSpeed / seen.size();</p>

  <p>// Rule 2: Move towards the perceived</p>

  <p>// center of gravity of the herd:</p>

  <p>currentDirection =</p>

  <p>totalBearing / seen.size();</p>

  <p>// Rule 3: Maintain a minimum distance</p>

  <p>// from those around you:</p>

  <p>if(distanceToNearest &lt;=</p>

  <p>field.minimumDistance) {</p>

  <p>currentDirection =</p>

  <p>nearestBeast.currentDirection;</p>

  <p>currentSpeed = nearestBeast.currentSpeed;</p>

  <p>if(currentSpeed &gt; field.maxSpeed) {</p>

  <p>currentSpeed = field.maxSpeed;</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>else { // You are in front, so slow down</p>

  <p>currentSpeed =</p>

  <p>(int)(currentSpeed * field.decayRate);</p>

  <p>}</p>

  <p>// Make the beast move:</p>

  <p>x += (int)(Math.cos(currentDirection)</p>

  <p>* currentSpeed);</p>

  <p>y += (int)(Math.sin(currentDirection)</p>

  <p>* currentSpeed);</p>

  <p>x %= field.xExtent;</p>

  <p>y %= field.yExtent;</p>

  <p>if(x &lt; 0)</p>

  <p>x += field.xExtent;</p>

  <p>if(y &lt; 0)</p>

  <p>y += field.yExtent;</p>

  <p>}</p>

  <p>public float bearingFromPointAlongAxis (</p>

  <p>int originX, int originY, float axis) {</p>

  <p>// Returns bearing angle of the current Beast</p>

  <p>// in the world coordiante system</p>

  <p>try {</p>

  <p>double bearingInRadians =</p>

  <p>Math.atan(</p>

  <p>(this.y - originY) /</p>

  <p>(this.x - originX));</p>

  <p>// Inverse tan has two solutions, so you</p>

  <p>// have to correct for other quarters:</p>

  <p>if(x &lt; originX) {</p>

  <p>if(y &lt; originY) {</p>

  <p>bearingInRadians += - (float)Math.PI;</p>

  <p>}</p>

  <p>else {</p>

  <p>bearingInRadians =</p>

  <p>(float)Math.PI - bearingInRadians;</p>

  <p>}</p>

  <p>}</p>

  <p>// Just subtract the axis (in radians):</p>

  <p>return (float) (axis - bearingInRadians);</p>

  <p>} catch(ArithmeticException aE) {</p>

  <p>// Divide by 0 error possible on this</p>

  <p>if(x &gt; originX) {</p>

  <p>return 0;</p>

  <p>}</p>

  <p>else</p>

  <p>return (float) Math.PI;</p>

  <p>}</p>

  <p>}</p>

  <p>public float distanceFromPoint(int x1, int y1){</p>

  <p>return (float) Math.sqrt(</p>

  <p>Math.pow(x1 - x, 2) +</p>

  <p>Math.pow(y1 - y, 2));</p>

  <p>}</p>

  <p>public Point position() {</p>

  <p>return new Point(x, y);</p>

  <p>}</p>

  <p>// Beasts know how to draw themselves:</p>

  <p>public void draw(Graphics g) {</p>

  <p>g.setColor(color);</p>

  <p>int directionInDegrees = (int)(</p>

  <p>(currentDirection * 360) / (2 * Math.PI));</p>

  <p>int startAngle = directionInDegrees -</p>

  <p>FieldOBeasts.halfFieldOfView;</p>

  <p>int endAngle = 90;</p>

  <p>g.fillArc(x, y, GSIZE, GSIZE,</p>

  <p>startAngle, endAngle);</p>

  <p>}</p>

  <p>}</p>

  <p>public class FieldOBeasts extends Applet</p>

  <p>implements Runnable {</p>

  <p>private Vector beasts;</p>

  <p>static float</p>

  <p>fieldOfView =</p>

  <p>(float) (Math.PI / 4), // In radians</p>

  <p>// Deceleration % per second:</p>

  <p>decayRate = 1.0f,</p>

  <p>minimumDistance = 10f; // In pixels</p>

  <p>static int</p>

  <p>halfFieldOfView = (int)(</p>

  <p>(fieldOfView * 360) / (2 * Math.PI)),</p>

  <p>xExtent = 0,</p>

  <p>yExtent = 0,</p>

  <p>numBeasts = 50,</p>

  <p>maxSpeed = 20; // Pixels/second</p>

  <p>boolean uniqueColors = true;</p>

  <p>Thread thisThread;</p>

  <p>int delay = 25;</p>

  <p>public void init() {</p>

  <p>if (xExtent == 0 &amp;&amp; yExtent == 0) {</p>

  <p>xExtent = Integer.parseInt(</p>

  <p>getParameter("xExtent"));</p>

  <p>yExtent = Integer.parseInt(</p>

  <p>getParameter("yExtent"));</p>

  <p>}</p>

  <p>beasts =</p>

  <p>makeBeastVector(numBeasts, uniqueColors);</p>

  <p>// Now start the beasts a-rovin':</p>

  <p>thisThread = new Thread(this);</p>

  <p>thisThread.start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>for(int i = 0; i &lt; beasts.size(); i++){</p>

  <p>Beast b = (Beast) beasts.elementAt(i);</p>

  <p>b.step();</p>

  <p>}</p>

  <p>try {</p>

  <p>thisThread.sleep(delay);</p>

  <p>} catch(InterruptedException ex){}</p>

  <p>repaint(); // Otherwise it won't update</p>

  <p>}</p>

  <p>}</p>

  <p>Vector makeBeastVector(</p>

  <p>int quantity, boolean uniqueColors) {</p>

  <p>Vector newBeasts = new Vector();</p>

  <p>Random generator = new Random();</p>

  <p>// Used only if uniqueColors is on:</p>

  <p>double cubeRootOfBeastNumber =</p>

  <p>Math.pow((double)numBeasts, 1.0 / 3.0);</p>

  <p>float colorCubeStepSize =</p>

  <p>(float) (1.0 / cubeRootOfBeastNumber);</p>

  <p>float r = 0.0f;</p>

  <p>float g = 0.0f;</p>

  <p>float b = 0.0f;</p>

  <p>for(int i = 0; i &lt; quantity; i++) {</p>

  <p>int x =</p>

  <p>(int) (generator.nextFloat() * xExtent);</p>

  <p>if(x &gt; xExtent - Beast.GSIZE)</p>

  <p>x -= Beast.GSIZE;</p>

  <p>int y =</p>

  <p>(int) (generator.nextFloat() * yExtent);</p>

  <p>if(y &gt; yExtent - Beast.GSIZE)</p>

  <p>y -= Beast.GSIZE;</p>

  <p>float direction = (float)(</p>

  <p>generator.nextFloat() * 2 * Math.PI);</p>

  <p>int speed = (int)(</p>

  <p>generator.nextFloat() * (float)maxSpeed);</p>

  <p>if(uniqueColors) {</p>

  <p>r += colorCubeStepSize;</p>

  <p>if(r &gt; 1.0) {</p>

  <p>r -= 1.0f;</p>

  <p>g += colorCubeStepSize;</p>

  <p>if( g &gt; 1.0) {</p>

  <p>g -= 1.0f;</p>

  <p>b += colorCubeStepSize;</p>

  <p>if(b &gt; 1.0)</p>

  <p>b -= 1.0f;</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>newBeasts.addElement(</p>

  <p>new Beast(this, x, y, direction, speed,</p>

  <p>new Color(r,g,b)));</p>

  <p>}</p>

  <p>return newBeasts;</p>

  <p>}</p>

  <p>public Vector beastListInSector(Beast viewer) {</p>

  <p>Vector output = new Vector();</p>

  <p>Enumeration e = beasts.elements();</p>

  <p>Beast aBeast = (Beast)beasts.elementAt(0);</p>

  <p>int counter = 0;</p>

  <p>while(e.hasMoreElements()) {</p>

  <p>aBeast = (Beast) e.nextElement();</p>

  <p>if(aBeast != viewer) {</p>

  <p>Point p = aBeast.position();</p>

  <p>Point v = viewer.position();</p>

  <p>float bearing =</p>

  <p>aBeast.bearingFromPointAlongAxis(</p>

  <p>v.x, v.y, viewer.currentDirection);</p>

  <p>if(Math.abs(bearing) &lt; fieldOfView / 2)</p>

  <p>output.addElement(aBeast);</p>

  <p>}</p>

  <p>}</p>

  <p>return output;</p>

  <p>}</p>

  <p>public void paint(Graphics g) {</p>

  <p>Enumeration e = beasts.elements();</p>

  <p>while(e.hasMoreElements()) {</p>

  <p>((Beast)e.nextElement()).draw(g);</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>FieldOBeasts field = new FieldOBeasts();</p>

  <p>field.xExtent = 640;</p>

  <p>field.yExtent = 480;</p>

  <p>Frame frame = new Frame("Field 'O Beasts");</p>

  <p>// Optionally use a command-line argument</p>

  <p>// for the sleep time:</p>

  <p>if(args.length &gt;= 1)</p>

  <p>field.delay = Integer.parseInt(args[0]);</p>

  <p>frame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>frame.add(field, BorderLayout.CENTER);</p>

  <p>frame.setSize(640,480);</p>

  <p>field.init();</p>

  <p>field.start();</p>

  <p>frame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>尽管这并非对Craig Reynold的“Boids”例子中的行为完美重现，但它却展现出了自己独有的迷人之外。通过对数字进行调整，即可进行全面的修改。至于与这种群聚行为有关的更多的情况，大家可以访问Craig Reynold的主页――在那个地方，甚至还提供了Boids一个公开的3D展示版本：</p>

  <p>http://www.hmt.com/cwr/boids.html</p>

  <p>为了将这个程序作为一个程序片运行，请在HTML文件中设置下述程序片标志：</p>

  <p>&lt;applet</p>

  <p>code=FieldOBeasts</p>

  <p>width=640</p>

  <p>height=480&gt;</p>

  <p>&lt;param name=xExtent value = "640"&gt;</p>

  <p>&lt;param name=yExtent value = "480"&gt;</p>

  <p>&lt;/applet&gt;</p>

  <p>17.4 总结</p>

  <p>通过本章的学习，大家知道运用Java可做到一些较复杂的事情。通过这些例子亦可看出，尽管Java必定有自己的局限，但受那些局限影响的主要是性能（比如写好文字处理程序后，会发现C++的版本要快得多――这部分是由于IO库做得不完善造成的；而在你读到本书的时候，情况也许已发生了变化。但Java的局限也仅此而已，它在语言表达方面的能力是无以伦比的。利用Java，几乎可以表达出我们想得到的任何事情。而与此同时，Java在表达的方便性和易读性上，也做足了功夫。所以在使用Java时，一般不会陷入其他语言常见的那种复杂境地。使用那些语言时，会感觉它们象一个爱唠叨的老太婆，哪有Java那样清纯、简练！而且通过Java 1.2的JFC/Swing库，AWT的表达能力和易用性甚至又得到了进一步的增强。</p>

  <p>17.5 练习</p>

  <p>(1) （稍微有些难度）改写FieldOBeasts.java，使它的状态能够保持固定。加上一些按钮，允许用户保存和恢复不同的状态文件，并从它们断掉的地方开始继续运行。请先参考第10章的CADState.java，再决定具体怎样做。</p>

  <p>(2) （大作业）以FieldOBeasts.java作为起点，构造一个自动化交通仿真系统。</p>

  <p>(3) （大作业）以ClassScanner.java作为起点，构造一个特殊的工具，用它找出那些虽然定义但从未用过的方法和字段。</p>

  <p>(4) （大作业）利用JDBC，构造一个联络管理程序。让这个程序以一个平面文件数据库为基础，其中包含了名字、地址、电话号码、E-mail地址等联系资料。应该能向数据库里方便地加入新名字。键入要查找的名字时，请采用在第15章的VLookup.java里介绍过的那种名字自动填充技术。英文版主页 | 中文版主页 | 详细目录 | 关于译者</p>

  <p>-------------------------------------------------</p>

  <p>TXT书库 www.16txt.com</p>

  <p>-------------------------------------------------附录A 使用非JAVA代码</p>

  <p>JAVA语言及其标准API（应用程序编程接口）应付应用程序的编写已绰绰有余。但在某些情况下，还是必须使用非JAVA编码。例如，我们有时要访问操作系统的专用特性，与特殊的硬件设备打交道，重复使用现有的非Java接口，或者要使用“对时间敏感”的代码段，等等。与非Java代码的沟通要求获得编译器和“虚拟机”的专门支持，并需附加的工具将Java代码映射成非Java代码（也有一个简单方法：在第15章的“一个Web应用”小节中，有个例子解释了如何利用标准输入输出同非Java代码连接）。目前，不同的开发商为我们提供了不同的方案：Java 1.1有“Java固有接口”（Java Native Interface，JNI），网景提出了自己的“Java运行期接口”（Java Runtime Interface）计划，而微软提供了J/Direct、“本源接口”（Raw Native Interface，RNI）以及Java/COM集成方案。</p>

  <p>各开发商在这个问题上所持的不同态度对程序员是非常不利的。若Java应用必须调用固有方法，则程序员或许要实现固有方法的不同版本――具体由应用程序运行的平台决定。程序员也许实际需要不同版本的Java代码，以及不同的Java虚拟机。</p>

  <p>另一个方案是CORBA（通用对象请求代理结构），这是由OMG（对象管理组，一家非赢利性的公司协会）开发的一种集成技术。CORBA并非任何语言的一部分，只是实现通用通信总线及服务的一种规范。利用它可在由不同语言实现的对象之间实现“相互操作”的能力。这种通信总线的名字叫作ORB（对象请求代理），是由其他开发商实现的一种产品，但并不属于Java语言规范的一部分。</p>

  <p>本附录将对JNI，J/DIRECT，RNI，JAVA/COM集成和CORBA进行概述。但不会作更深层次的探讨，甚至有时还假定读者已对相关的概念和技术有了一定程度的认识。但到最后，大家应该能够自行比较不同的方法，并根据自己要解决的问题挑选出最恰当的一种。</p>

  <p>A.1 Java固有接口</p>

  <p>JNI是一种包容极广的编程接口，允许我们从Java应用程序里调用固有方法。它是在Java 1.1里新增的，维持着与Java 1.0的相应特性――“固有方法接口”（NMI）――某种程度的兼容。NMI设计上一些特点使其未获所有虚拟机的支持。考虑到这个原因，Java语言将来的版本可能不再提供对NMI的支持，这儿也不准备讨论它。</p>

  <p>目前，JNI只能与用C或C++写成的固有方法打交道。利用JNI，我们的固有方法可以：</p>

  <p>■创建、检查及更新Java对象（包括数组和字串）</p>

  <p>■调用Java方法</p>

  <p>■俘获和丢弃“异常”</p>

  <p>■装载类并获取类信息</p>

  <p>■进行运行期类型检查</p>

  <p>所以，原来在Java中能对类及对象做的几乎所有事情在固有方法中同样可以做到。</p>

  <p>A.1.1 调用固有方法</p>

  <p>我们先从一个简单的例子开始：一个Java程序调用固有方法，后者再调用Win32的API函数MessageBox()，显示出一个图形化的文本框。这个例子稍后也会与J/Direct一志使用。若您的平台不是Win32，只需将包含了下述内容的C头：</p>

  <p>#include &lt;windows.h&gt;</p>

  <p>替换成：</p>

  <p>#include &lt;stdio.h&gt;</p>

  <p>并将对MessageBox()的调用换成调用printf()即可。</p>

  <p>第一步是写出对固有方法及它的自变量进行声明的Java代码：</p>

  <p>class ShowMsgBox {</p>

  <p>public static void main(String [] args) {</p>

  <p>ShowMsgBox app = new ShowMsgBox();</p>

  <p>app.ShowMessage("Generated with JNI");</p>

  <p>}</p>

  <p>private native void ShowMessage(String msg);</p>

  <p>static {</p>

  <p>System.loadLibrary("MsgImpl");</p>

  <p>}</p>

  <p>}</p>

  <p>在固有方法声明的后面，跟随有一个static代码块，它会调用System.loadLibrary()（可在任何时候调用它，但这样做更恰当）System.loadLibrary()将一个DLL载入内存，并建立同它的链接。DLL必须位于您的系统路径，或者在包含了Java类文件的目录中。根据具体的平台，JVM会自动添加适当的文件扩展名。</p>

  <p>1. C头文件生成器：javah</p>

  <p>现在编译您的Java源文件，并对编译出来的.class文件运行javah。javah是在1.0版里提供的，但由于我们要使用Java 1.1 JNI，所以必须指定-jni参数：</p>

  <p>javah -jni ShowMsgBox</p>

  <p>javah会读入类文件，并为每个固有方法声明在C或C++头文件里生成一个函数原型。下面是输出结果――ShowMsgBox.h源文件（为符合本书的要求，稍微进行了一下修改）：</p>

  <p>/* DO NOT EDIT THIS FILE</p>

  <p>- it is machine generated */</p>

  <p>#include &lt;jni.h&gt;</p>

  <p>/* Header for class ShowMsgBox */</p>

  <p>#ifndef _Included_ShowMsgBox</p>

  <p>#define _Included_ShowMsgBox</p>

  <p>#ifdef __cplusplus</p>

  <p>extern "C" {</p>

  <p>#endif</p>

  <p>/*</p>

  <p>* Class: ShowMsgBox</p>

  <p>* Method: ShowMessage</p>

  <p>* Signature: (Ljava/lang/String;)V</p>

  <p>*/</p>

  <p>JNIEXPORT void JNICALL</p>

  <p>Java_ShowMsgBox_ShowMessage</p>

  <p>(JNIEnv *, jobject, jstring);</p>

  <p>#ifdef __cplusplus</p>

  <p>}</p>

  <p>#endif</p>

  <p>#endif</p>

  <p>从“#ifdef_cplusplus”这个预处理引导命令可以看出，该文件既可由C编译器编译，亦可由C++编译器编译。第一个#include命令包括jni.h――一个头文件，作用之一是定义在文件其余部分用到的类型；JNIEXPORT和JNICALL是一些宏，它们进行了适当的扩充，以便与那些不同平台专用的引导命令配合；JNIEnv，jobject以及jstring则是JNI数据类型定义。</p>

  <p>2. 名称管理和函数签名</p>

  <p>JNI统一了固有方法的命名规则；这一点是非常重要的，因为它属于虚拟机将Java调用与固有方法链接起来的机制的一部分。从根本上说，所有固有方法都要以一个“Java”起头，后面跟随Java方法的名字；下划线字符则作为分隔符使用。若Java固有方法“过载”（即命名重复），那么也把函数签名追加到名字后面。在原型前面的注释里，大家可看到固有的签名。欲了解命名规则和固有方法签名更详细的情况，请参考相应的JNI文档。</p>

  <p>3. 实现自己的DLL</p>

  <p>此时，我们要做的全部事情就是写一个C或C++源文件，在其中包含由javah生成的头文件；并实现固有方法；然后编译它，生成一个动态链接库。这一部分的工作是与平台有关的，所以我假定读者已经知道如何创建一个DLL。通过调用一个Win32 API，下面的代码实现了固有方法。随后，它会编译和链接到一个名为MsgImpl.dll的文件里：</p>

  <p>#include &lt;windows.h&gt;</p>

  <p>#include "ShowMsgBox.h"</p>

  <p>BOOL APIENTRY DllMain(HANDLE hModule,</p>

  <p>DWORD dwReason, void** lpReserved) {</p>

  <p>return TRUE;</p>

  <p>}</p>

  <p>JNIEXPORT void JNICALL</p>

  <p>Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv,</p>

  <p>jobject this, jstring jMsg) {</p>

  <p>const char * msg;</p>

  <p>msg = (*jEnv)-&gt;GetStringUTFChars(jEnv, jMsg,0);</p>

  <p>MessageBox(HWND_DESKTOP, msg,</p>

  <p>"Thinking in Java: JNI",</p>

  <p>MB_OK | MB_ICONEXCLAMATION);</p>

  <p>(*jEnv)-&gt;ReleaseStringUTFChars(jEnv, jMsg,msg);</p>

  <p>}</p>

  <p>若对Win32没有兴趣，只需跳过MessageBox()调用；最有趣的部分是它周围的代码。传递到固有方法内部的自变量是返回Java的大门。第一个自变量是类型JNIEnv的，其中包含了回调JVM需要的所有挂钩（下一节再详细讲述）。由于方法的类型不同，第二个自变量也有自己不同的含义。对于象上例那样的非static方法（也叫作实例方法），第二个自变量等价于C++的“this”指针，并类似于Java的“this”：都引用了调用固有方法的那个对象。对于static方法，它是对特定Class对象的一个引用，方法就是在那个Class对象里实现的。</p>

  <p>剩余的自变量代表传递到固有方法调用里的Java对象。主类型也是以这种形式传递的，但它们进行的“按值”传递。</p>

  <p>在后面的小节里，我们准备讲述如何从一个固有方法的内部访问和控制JVM，同时对上述代码进行更详尽的解释。</p>

  <p>A.1.2 访问JNI函数：JNIEnv自变量</p>

  <p>利用JNI函数，程序员可从一个固有方法的内部与JVM打交道。正如大家在前面的例子中看到的那样，每个JNI固有方法都会接收一个特殊的自变量作为自己的第一个参数：JNIEnv自变量――它是指向类型为JNIEnv_的一个特殊JNI数据结构的指针。JNI数据结构的一个元素是指向由JVM生成的一个数组的指针；该数组的每个元素都是指向一个JNI函数的指针。可从固有方法的内部发出对JNI函数的调用，做法是撤消对这些指针的引用（具体的操作实际很简单）。每种JVM都以自己的方式实现了JNI函数，但它们的地址肯定位于预先定义好的偏移处。</p>

  <p>利用JNIEnv自变量，程序员可访问一系列函数。这些函数可划分为下述类别：</p>

  <p>■获取版本信息</p>

  <p>■进行类和对象操作</p>

  <p>■控制对Java对象的全局和局部引用</p>

  <p>■访问实例字段和静态字段</p>

  <p>■调用实例方法和静态方法</p>

  <p>■执行字串和数组操作</p>

  <p>■产生和控制Java异常</p>

  <p>JNI函数的数量相当多，这里不再详述。相反，我会向大家揭示使用这些函数时背后的一些基本原理。欲了解更详细的情况，请参阅自己所用编译器的JNI文档。</p>

  <p>若观察一下jni.h头文件，就会发现在#ifdef _cplusplus预处理器条件的内部，当由C++编译器编译时，JNIEnv_结构被定义成一个类。这个类包含了大量内嵌函数。通过一种简单而且熟悉的语法，这些函数让我们可以从容访问JNI函数。例如，前例包含了下面这行代码：</p>

  <p>(*jEnv)-&gt;ReleaseStringUTFChars(jEnv, jMsg,msg);</p>

  <p>它在C++里可改写成下面这个样子：</p>

  <p>jEnv-&gt;ReleaseStringUTFChars(jMsg,msg);</p>

  <p>大家可注意到自己不再需要同时撤消对jEnv的两个引用，相同的指针不再作为第一个参数传递给JNI函数调用。在这些例子剩下的地方，我会使用C++风格的代码。</p>

  <p>1. 访问Java字串</p>

  <p>作为访问JNI函数的一个例子，请思考上述的代码。在这里，我们利用JNIEnv的自变量jEnv来访问一个Java字串。Java字串采取的是Unicode格式，所以假若收到这样一个字串，并想把它传给一个非Unicode函数（如printf()），首先必须用JNI函数GetStringUTFChars()将其转换成ASCII字符。该函数能接收一个Java字串，然后把它转换成UTF-8字符（用8位宽度容纳ASCII值，或用16位宽度容纳Unicode；若原始字串的内容完全由ASCII构成，那么结果字串也是ASCII）。</p>

  <p>GetStringUTFChars是JNIEnv间接指向的那个结构里的一个字段，而这个字段又是指向一个函数的指针。为访问JNI函数，我们用传统的C语法来调用一个函数（通过指针）。利用上述形式可实现对所有JNI函数的访问。</p>

  <p>A.1.3 传递和使用Java对象</p>

  <p>在前例中，我们将一个字串传递给固有方法。事实上，亦可将自己创建的Java对象传递给固有方法。</p>

  <p>在我们的固有方法内部，可访问已收到的那些对象的字段及方法。</p>

  <p>为传递对象，声明固有方法时要采用原始的Java语法。如下例所示，MyJavaClass有一个public（公共）字段，以及一个public方法。UseObjects类声明了一个固有方法，用于接收MyJavaClass类的一个对象。为调查固有方法是否能控制自己的自变量，我们设置了自变量的public字段，调用固有方法，然后打印出public字段的值。</p>

  <p>class MyJavaClass {</p>

  <p>public void divByTwo() { aValue /= 2; }</p>

  <p>public int aValue;</p>

  <p>}</p>

  <p>public class UseObjects {</p>

  <p>public static void main(String [] args) {</p>

  <p>UseObjects app = new UseObjects();</p>

  <p>MyJavaClass anObj = new MyJavaClass();</p>

  <p>anObj.aValue = 2;</p>

  <p>app.changeObject(anObj);</p>

  <p>System.out.println("Java: " + anObj.aValue);</p>

  <p>}</p>

  <p>private native void</p>

  <p>changeObject(MyJavaClass obj);</p>

  <p>static {</p>

  <p>System.loadLibrary("UseObjImpl");</p>

  <p>}</p>

  <p>}</p>

  <p>编译好代码，并将.class文件传递给javah后，就可以实现固有方法。在下面这个例子中，一旦取得字段和方法ID，就会通过JNI函数访问它们。</p>

  <p>JNIEXPORT void JNICALL</p>

  <p>Java_UseObjects_changeObject(</p>

  <p>JNIEnv * env, jobject jThis, jobject obj) {</p>

  <p>jclass cls;</p>

  <p>jfieldID fid;</p>

  <p>jmethodID mid;</p>

  <p>int value;</p>

  <p>cls = env-&gt;GetObjectClass(obj);</p>

  <p>fid = env-&gt;GetFieldID(cls,</p>

  <p>"aValue", "I");</p>

  <p>mid = env-&gt;GetMethodID(cls,</p>

  <p>"divByTwo", "()V");</p>

  <p>value = env-&gt;GetIntField(obj, fid);</p>

  <p>printf("Native: %d\n", value);</p>

  <p>env-&gt;SetIntField(obj, fid, 6);</p>

  <p>env-&gt;CallVoidMethod(obj, mid);</p>

  <p>value = env-&gt;GetIntField(obj, fid);</p>

  <p>printf("Native: %d\n", value);</p>

  <p>}</p>

  <p>除第一个自变量外，C++函数会接收一个jobject，它代表Java对象引用“固有”的那一面――那个引用是我们从Java代码里传递的。我们简单地读取aValue，把它打印出来，改变这个值，调用对象的divByTwo()方法，再将值重新打印一遍。</p>

  <p>为访问一个字段或方法，首先必须获取它的标识符。利用适当的JNI函数，可方便地取得类对象、元素名以及签名信息。这些函数会返回一个标识符，利用它可访问对应的元素。尽管这一方式显得有些曲折，但我们的固有方法确实对Java对象的内部布局一无所知。因此，它必须通过由JVM返回的索引访问字段和方法。这样一来，不同的JVM就可实现不同的内部对象布局，同时不会对固有方法造成影响。</p>

  <p>若运行Java程序，就会发现从Java那一侧传来的对象是由我们的固有方法处理的。但传递的到底是什么呢？是指针，还是Java引用？而且垃圾收集器在固有方法调用期间又在做什么呢？</p>

  <p>垃圾收集器会在固有方法执行期间持续运行，但在一次固有方法调用期间，我们的对象可保证不会被当作“垃圾”收集去。为确保这一点，事先创建了“局部引用”，并在固有方法调用之后立即清除。由于它们的“生命期”与调用过程息息相关，所以能够保证对象在固有方法调用期间的有效性。</p>

  <p>由于这些引用会在每次函数调用的时候创建和破坏，所以不可在static变量中制作固有方法的局部副本（本地拷贝）。若希望一个引用在函数存在期间持续有效，就需要一个全局引用。全局引用不是由JVM创建的，但通过调用特定的JNI函数，程序员可将局部引用扩展为全局引用。创建一个全局引用时，需对引用对象的“生存时间”负责。全局引用（以及它引用的对象）会一直留在内存里，直到用特定的JNI函数明确释放了这个引用。它类似于C的malloc()和free()。</p>

  <p>A.1.4 JNI和Java异常</p>

  <p>利用JNI，可丢弃、捕捉、打印以及重新丢弃Java异常，就象在一个Java程序里那样。但对程序员来说，需自行调用专用的JNI函数，以便对异常进行处理。下面列出用于异常处理的一些JNI函数：</p>

  <p>■Throw()：丢弃一个现有的异常对象；在固有方法中用于重新丢弃一个异常。</p>

  <p>■ThrowNew()：生成一个新的异常对象，并将其丢弃。</p>

  <p>■ExceptionOccurred()：判断一个异常是否已被丢弃，但尚未清除。</p>

  <p>■ExceptionDescribe()：打印一个异常和堆栈跟踪信息。</p>

  <p>■ExceptionClear()：清除一个待决的异常。</p>

  <p>■FatalError()：造成一个严重错误，不返回。</p>

  <p>在所有这些函数中，最不能忽视的就是ExceptionOccurred()和ExceptionClear()。大多数JNI函数都能产生异常，而且没有象在Java的try块内的那种语言特性可供利用。所以在每一次JNI函数调用之后，都必须调用ExceptionOccurred()，了解异常是否已被丢弃。若侦测到一个异常，可选择对其加以控制（可能时还要重新丢弃它）。然而，必须确保异常最终被清除。这可以在自己的函数中用ExceptionClear()来实现；若异常被重新丢弃，也可能在其他某些函数中进行。但无论如何，这一工作是必不可少的。</p>

  <p>我们必须保证异常被彻底清除。否则，假若在一个异常待决的情况下调用一个JNI函数，获得的结果往往是无法预知的。也有少数几个JNI函数可在异常时安全调用；当然，它们都是专门的异常控制函数。</p>

  <p>A.1.5 JNI和线程处理</p>

  <p>由于Java是一种多线程语言，几个线程可能同时发出对一个固有方法的调用（若另一个线程发出调用，固有方法可能在运行期间暂停）。此时，完全要由程序员来保证固有调用在多线程的环境中安全进行。例如，要防范用一种未进行监视的方法修改共享数据。此时，我们主要有两个选择：将固有方法声明为“同步”，或在固有方法内部采取其他某些策略，确保数据处理正确地并发进行。</p>

  <p>此外，绝对不要通过线程传递JNIEnv，因为它指向的内部结构是在“每线程”的基础上分配的，而且包含了只对那些特定的线程才有意义的信息。</p>

  <p>A.1.6 使用现成代码</p>

  <p>为实现JNI固有方法，最简单的方法就是在一个Java类里编写固有方法的原型，编译那个类，再通过javah运行.class文件。但假若我们已有一个大型的、早已存在的代码库，而且想从Java里调用它们，此时又该如何是好呢？不可将DLL中的所有函数更名，使其符合JNI命名规则，这种方案是不可行的。最好的方法是在原来的代码库“外面”写一个封装DLL。Java代码会调用新DLL里的函数，后者再调用原始的DLL函数。这个方法并非仅仅是一种解决方案；大多数情况下，我们甚至必须这样做，因为必须面向对象引用调用JNI函数，否则无法使用它们。</p>

  <p>A.2 微软的解决方案</p>

  <p>到本书完稿时为止，微软仍未提供对JNI的支持，只是用自己的专利方法提供了对非Java代码调用的支持。这一支持内建到编译器Microsoft JVM以及外部工具中。只有程序用Microsoft Java编译器编译，而且只有在Microsoft Java虚拟机（JVM）上运行的时候，本节讲述的特性才会有效。若计划在因特网上发行自己的应用，或者本单位的内联网建立在不同平台的基础上，就可能成为一个严重的问题。</p>

  <p>微软与Win32代码的接口为我们提供了连接Win32的三种途径：</p>

  <p>(1) J/Direct：方便调用Win32 DLL函数的一种途径，具有某些限制。</p>

  <p>(2) 本原接口（RNI）：可调用Win32 DLL函数，但必须自行解决“垃圾收集”问题。</p>

  <p>(3) Java/COM集成：可从Java里直接揭示或调用COM服务。</p>

  <p>后续的小节将分别探讨这三种技术。</p>

  <p>写作本书的时候，这些特性均通过了Microsoft SDK for Java 2.0 beta 2的支持。可从微软公司的Web站点下载这个开发平台（要经历一个痛苦的选择过程，他们叫作“Active Setup”）。Java SDK是一套命令行工具的集合，但编译引擎可轻易嵌入Developer Studio环境，以便我们用Visual J++ 1.1来编译Java 1.1代码。</p>

  <p>A.3 J/Direct</p>

  <p>J/Direct是调用Win32 DLL函数最简单的方式。它的主要设计目标是与Win32API打交道，但完全可用它调用其他任何API。但是，尽管这一特性非常方便，但它同时也造成了某些限制，且降低了性能（与RNI相比）。但J/Direct也有一些明显的优点。首先，除希望调用的那个DLL里的代码之外，没有必要再编写额外的非Java代码，换言之，我们不需要一个封装器或者代理／存根DLL。其次，函数自变量与标准数据类型之间实现了自动转换。若必须传递用户自定义的数据类型，那么J/Direct可能不按我们的希望工作。第三，就象下例展示的那样，它非常简单和直接。只需少数几行，这个例子便能调用Win32 API函数MessageBox()，它能弹出一个小的模态窗口，并带有一个标题、一条消息、一个可选的图标以及几个按钮。</p>

  <p>public class ShowMsgBox {</p>

  <p>public static void main(String args[])</p>

  <p>throws UnsatisfiedLinkError {</p>

  <p>MessageBox(0,</p>

  <p>"Created by the MessageBox() Win32 func",</p>

  <p>"Thinking in Java", 0);</p>

  <p>}</p>

  <p>/** @dll.import("USER32") */</p>

  <p>private static native int</p>

  <p>MessageBox(int hwndOwner, String text,</p>

  <p>String title, int fuStyle);</p>

  <p>}</p>

  <p>令人震惊的是，这里便是我们利用J/Direct调用Win32 DLL函数所需的全部代码。其中的关键是位于示范代码底部的MessageBox()声明之前的@dll.import引导命令。它表面上看是一条注释，但实际并非如此。它的作用是告诉编译器：引导命令下面的函数是在USER32 DLL里实现的，而且应相应地调用。我们要做的全部事情就是提供与DLL内实现的函数相符的一个原型，并调用函数。但是毋需在Java版本里手工键入需要的每一个Win32 API函数，一个Microsoft Java包会帮我们做这件事情（很快就会详细解释）。为了让这个例子正常工作，函数必须“按名称”由DLL导出。但是，也可以用@dll.import引导命令“按顺序”链接。举个例子来说，我们可指定函数在DLL里的入口位置。稍后还会具体讲述@dll.import引导命令的特性。</p>

  <p>用非Java代码进行链接的一个重要问题就是函数参数的自动配置。正如大家看到的那样，MessageBox()的Java声明采用了两个字串自变量，但原来的C方案则采用了两个char指针。编译器会帮助我们自动转换标准数据类型，同时遵照本章后一节要讲述的规则。</p>

  <p>最好，大家或许已注意到了main()声明中的UnsatisfiedLinkError异常。在运行期的时候，一旦链接程序不能从非Java函数里解析出符号，就会触发这一异常（事件）。这可能是由多方面的原因造成的：.dll文件未找到；不是一个有效的DLL；或者J/Direct未获您所使用的虚拟机的支持。为了使DLL能被找到，它必须位于Windows或Windows\System目录下，位于由PATH环境变量列出的一个目录中，或者位于和.class文件相同的目录。J/Direct获得了Microsoft Java编译器1.02.4213版本及更高版本的支持，也获得了Microsoft JVM 4.79.2164及更高版本的支持。为了解自己编译器的版本号，请在命令行下运行JVC，不要加任何参数。为了解JVM的版本号，请找到msjava.dll的图标，并利用右键弹出菜单观察它的属性。</p>

  <p>A.3.1 @dll.import引导命令</p>

  <p>作为使用J/Direct唯一的途径，@dll.import引导命令相当灵活。它提供了为数众多的修改符，可用它们自定义同非Java代码建立链接关系的方式。它亦可应用于类内的一些方法，或应用于整个类。也就是说，我们在那个类内声明的所有方法都是在相同的DLL里实现的。下面让我们具体研究一下这些特性。</p>

  <p>1. 别名处理和按顺序链接</p>

  <p>为了使@dll.import引导命令能象上面显示的那样工作，DLL内的函数必须按名字导出。然而，我们有时想使用与DLL里原始名字不同的一个名字（别名处理），否则函数就可能按编号（比如按顺序）导出，而不是按名字导出。下面这个例子声明了FinestraDiMessaggio()（用意大利语说的“MessageBox”）。正如大家看到的那样，使用的语法是非常简单的。</p>

  <p>public class Aliasing {</p>

  <p>public static void main(String args[])</p>

  <p>throws UnsatisfiedLinkError {</p>

  <p>FinestraDiMessaggio(0,</p>

  <p>"Created by the MessageBox() Win32 func",</p>

  <p>"Thinking in Java", 0);</p>

  <p>}</p>

  <p>/** @dll.import("USER32",</p>

  <p>entrypoint="MessageBox") */</p>

  <p>private static native int</p>

  <p>FinestraDiMessaggio(int hwndOwner, String text,</p>

  <p>String title, int fuStyle);</p>

  <p>}</p>

  <p>下面这个例子展示了如何同DLL里并非按名字导出的一个函数建立链接，那个函数事实是按它们在DLL里的位置导出的。这个例子假设有一个名为MYMATH的DLL，这个DLL在位置编号3处包含了一个函数。那个函数获取两个整数作为自变量，并返回两个整数的和。</p>

  <p>public class ByOrdinal {</p>

  <p>public static void main(String args[])</p>

  <p>throws UnsatisfiedLinkError {</p>

  <p>int j=3, k=9;</p>

  <p>System.out.println("Result of DLL function:"</p>

  <p>+ Add(j,k));</p>

  <p>}</p>

  <p>/** @dll.import("MYMATH", entrypoint = "#3") */</p>

  <p>private static native int Add(int op1,int op2);</p>

  <p>}</p>

  <p>可以看出，唯一的差异就是entrypoint自变量的形式。</p>

  <p>2. 将@dll.import应用于整个类</p>

  <p>@dll.import引导命令可应用于整个类。也就是说，那个类的所有方法都是在相同的DLL里实现的，并具有相同的链接属性。引导命令不会由子类继承；考虑到这个原因，而且由于DLL里的函数是自然的static函数，所以更佳的设计方案是将API函数封装到一个独立的类里，如下所示：</p>

  <p>/** @dll.import("USER32") */</p>

  <p>class MyUser32Access {</p>

  <p>public static native int</p>

  <p>MessageBox(int hwndOwner, String text,</p>

  <p>String title, int fuStyle);</p>

  <p>public native static boolean</p>

  <p>MessageBeep(int uType);</p>

  <p>}</p>

  <p>public class WholeClass {</p>

  <p>public static void main(String args[])</p>

  <p>throws UnsatisfiedLinkError {</p>

  <p>MyUser32Access.MessageBeep(4);</p>

  <p>MyUser32Access.MessageBox(0,</p>

  <p>"Created by the MessageBox() Win32 func",</p>

  <p>"Thinking in Java"</p>

  <div class="mbppagebreak"></div>
</body>
</html>
